Profile picture

[Spring] MVC Configuration 학습테스트

Amaranth2023년 05월 04일

configuration 파일 설정해서 다음 3가지 미션 해결하기

  1. view controller 설정

    : “/” 요청 시 hello.html 페이지 응답하기(@GetMapping을 사용하지 않고)

  2. interceptor 설정

    : “/admin/**” 요청 시 LoginInterceptor가 동작하게 하기

  3. Argument Resolver 설정

    AuthenticationPrincipalArgumentResolver 등록하기

Spring MVC


Java Configuration 클래스를 통해 Spring MVC 지원을 활성화하려면 @Configuration 클래스에 @EnableWebMvc 주석을 추가해야 한다.

@EnableWebMvc
@Configuration
public class WebMvcConfiguration {
		//...
}

이렇게 하면 Controller 및 매핑 등록, 형식 변환기, 유효성 검사 지원, 예외 처리와 같은 MVC 프로젝트에 필요한 기본 지원이 설정된다.

WebMvcConfigurer 인터페이스를 구현하는 식으로 사용자 정의 Configuration을 만들면 훨씬 편리하다.

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
}

View Controllers 설정


미션 - “/” 요청 시 hello.html 페이지 응답하기

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("hello");
    }
		...
}

위 코드를 xml 파일로 작성한다면 다음과 같다.

<mvc:view-controller path="/" view-name="home"/>

ViewControllerRegistry

ViewControllerRegistry를 사용하면 URL과 View 이름 사이에 직접 매핑을 생성하는 View Controller를 등록할 수 있다.

이렇게 하면 둘 사이에 별도의 Controller 핸들러가 필요하지 않다.

Interceptor 설정


미션 - /admin/** 요청 시 LoginInterceptor가 동작하게 하기

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
		@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**");
    }
		...
}
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/admin/**"/>
        <bean class="nextstep.helloworld.mvcconfig.ui.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Interceptor란

Intercept란 사전적으로 가로채다라는 의미를 가지고 있다.

이 뜻과 같이 사용자가 서버로 요청을 보낼 때, 요청이 컨트롤러에 도달하기 전에 Request 객체를 낚아채서 개발자가 원하는 작업을 수행할 수 있도록 하는 것을 Interceptor(인터셉터)라고 한다.

Untitled

  • 이미지에 대한 보충 설명
    • Request 요청이 컨트롤러로 도달하는 과정 HTTP 요청WASFilter서블릿InterceptorController
    • Filter - 서블릿이 제공
    • Interceptor - 스프링 MVC가 제공

Interceptor의 활용

  • Interceptor는 이를테면 로그인 체크, 권한 체크와 같은 검증/필터링 기능을 수행하게 할 수 있다.
  • 매 컨트롤러에서 요청을 검증하는 작업을 Interceptor 동작으로 분리함으로써 코드중복을 줄일 수 있다.
  • 인터셉터는 url을 기준으로 적용할 수 있기 때문에 코드 누락에 대한 걱정을 덜 수 있다.

Interceptor 구현하기

Spring의 Interceptor는 HandlerInterceptorAdapter 클래스를 확장하거나 HandlerInterceptor 인터페이스를 구현하는 클래스이다.

public interface HandlerInterceptor {

		default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
				return true;
		}

		default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
		}

		default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
		}
}

이벤트 발생 시 콜백 함수를 실행시키는 이벤트 리스너처럼 사용할 수 있다.


학습 테스트 자료에서 제공하고 있는 LoginInterceptor 클래스는 다음과 같이 구현되어 있다.

HandlerInterceptorAdapter를 확장하고 있고, preHandle() 메서드를 오버라이딩하고 있다.

public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String accessToken = request.getHeader("Authorization");
        if (accessToken == null) {
            throw new AuthorizationException();
        }

        return super.preHandle(request, response, handler);
    }
}

이렇게 Interceptor 클래스를 구현해준 뒤, 앞에서 한 대로 @Configuration 클래스(혹은 xml 설정파일)에서 Interceptor 클래스를 Bean으로 등록하고 Interceptor를 적용할 url을 작성해주면 된다.

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
		@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**");
    }
		...
}

Argument Resolver 설정


미션 - AuthenticationPrincipalArgumentResolver 등록하기

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
		@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthenticationPrincipalArgumentResolver());
    }
}

Argument Resolver란?

Argument Resolver를 사용하면 컨트롤러 메서드의 파라미터 중 특정 조건에 맞는 파라미터가 있을 때 요청에 들어온 값을 이용해 원하는 객체를 만들어 바인딩(파싱)해줄 수 있다.

요청에 들어있는 데이터를 추출해 필요한 데이터 객체로 파싱한다.

Argument Resolver 클래스는 인터페이스 HandlerMethodArgumentResolver를 구현해야 한다.

사용자 정의 Argument Resolver 만들기

다음과 같이 Resolver를 거친 매개변수를 나타낼 어노테이션 @Version을 커스텀한다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrincipal {
    boolean required() default true;
}

이후 인터페이스 HandlerMethodArgumentResolver를 구현하여 커스텀 Argument Resolver를 만든다.

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        return new LoginMember(1L, "email", 120);
    }
}

마지막으로 @Configuration 클래스에 Resolver를 추가해준다.

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
		...
		@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthenticationPrincipalArgumentResolver());
    }
}

이렇게 하면 컨트롤러에서 다음과 같이 사용할 수 있다.

@RestController
public class MvcConfigController {
		@GetMapping("/members/me")
    public ResponseEntity<LoginMember> findMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
        return ResponseEntity.ok().body(loginMember);
    }
		...
}

Interceptor와 Argument Resolver 비교

둘은 요청이 컨트롤러에 도달하기 전에 Request를 처리한다는 점에서 유사한 역할을 수행한다.

다만 Interceptor는 실제 컨트롤러가 실행되기 전에 요청을 가로채며, 특정 객체를 반환할 수 없다(void).

반면 ArgumentResolver는 어떠한 요청이 컨트롤러에 들어온 직후 수행되며 요청 객체를 가공해서 필요한 객체로 변환할 수 있다.

참고 자료


Web on Servlet Stack

[Spring] 스프링 인터셉터(Interceptor)란 ?

[Spring Boot] 인터셉터 / Interceptor / 로그인 여부 체크하기

[Spring] Filter, Interceptor, Argument Resolver란?


Loading script...